package pers.brozen.anka.support.wechat;


import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import pers.brozen.anka.support.wechat.annotations.WeChatApi;
import pers.brozen.anka.support.wechat.cache.MetaInfoCache;
import pers.brozen.anka.support.wechat.cache.RequestMetaInfo;
import pers.brozen.anka.support.wechat.request.AccessTokenGetRequest;
import pers.brozen.anka.support.wechat.request.WeChatRequest;
import pers.brozen.anka.support.wechat.response.AccessTokenGetResponse;
import pers.brozen.anka.support.wechat.response.WeChatResponse;
import pers.brozen.anka.utils.HttpClientHolder;
import pers.brozen.anka.utils.json.JacksonUtils;

import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.util.Map;

/**
 * @author Brozen
 * @date 2019/8/15 3:16 PM
 */
@Slf4j
public abstract class WeChatAPI {

    @Getter
    private final String appId;

    @Getter
    private final String appSecret;

    private String accessToken;
    private long accessTokenExpireTs;

    public WeChatAPI(String appId, String appSecret) {
        this.appId = appId;
        this.appSecret = appSecret;
    }

    /**
     * 获取accessToken，如果本地缓存的accessToken未过期，则直接返回，否则从接口获取
     */
    public String getAccessToken() {
        if (System.currentTimeMillis() < accessTokenExpireTs) {
            return accessToken;
        } else {
            return getAccessTokenFromWeChat();
        }
    }

    /**
     * 从微信接口获取access_token，并缓存下来
     */
    public synchronized String getAccessTokenFromWeChat() {
        AccessTokenGetRequest request = new AccessTokenGetRequest();
        request.setAppid(appId);
        request.setAppSecret(appSecret);

        AccessTokenGetResponse response = request(request);
        if (response.getErrCode() != null && AccessTokenGetResponse.ErrorCode.SUCCESS != response.getErrCode()) {
            throw new WeChatException(String.valueOf(response.getErrCode()), response.getErrMsg());
        }

        this.accessToken = response.getAccessToken();
        this.accessTokenExpireTs = response.getExpiresIn();
        return accessToken;
    }

    protected void checkErrorResponse(String response) {
        if (StringUtils.isBlank(response) || !response.contains("errcode")) {
            return;
        }

        Map jo = JacksonUtils.parseObject(response, Map.class);
        Integer errorCode = (Integer) jo.get("errcode");
        if (!errorCode.equals(0)) {
            log.error("微信接口校验信息：" + response);
        }
    }

    protected void checkAndThrowErrorResponse(String response) {
        if (StringUtils.isBlank(response) || !response.contains("errcode")) {
            return;
        }

        Map jo = JacksonUtils.parseObject(response, Map.class);
        Integer errorCode = (Integer) jo.get("errcode");
        if (!errorCode.equals(0)) {
            log.error("微信接口校验信息：" + response);
            throw new WeChatException(errorCode.toString(), String.valueOf(jo.get("errmsg")));
        }
    }

    /**
     * http请求
     */
    public <T extends WeChatResponse> T request(WeChatRequest<T> request) {
        return sendRequest(request, HttpClientHolder.getClient());
    }

    /**
     * https 请求
     */
    public <T extends WeChatResponse> T httpsRequest(WeChatRequest<T> request, KeyStore keyStore, String password) throws Exception {
        HttpClientBuilder builder = HttpClients.custom();

        SSLContext sslcontext = SSLContexts.custom()
                .loadKeyMaterial(keyStore, password.toCharArray())//这里也是写密码的
                .build();
        // Allow TLSv1 protocol only
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
                sslcontext,
                new String[]{"TLSv1"},
                null,
                SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);


        builder.setSSLSocketFactory(sslsf);
        CloseableHttpClient client = builder.build();
        return sendRequest(request, client);
    }


    private <T extends WeChatResponse> T sendRequest(WeChatRequest<T> request, HttpClient client) {
        request.setAppid(this.appId);
        RequestMetaInfo requestInfo = MetaInfoCache.parseRequest(request.getClass());

        // 签名，如果是Signable且未设置签名，会通过sign方法生成签名并设置到request中
        signRequestIfNecessary(request);

        // 构造请求
        WeChatApi weChatApi = requestInfo.getWeChatApi();
        String url = weChatApi.url();
        HttpUriRequest httpRequest;
        if (weChatApi.method() == ApiRequestMethod.POST) {
            httpRequest = constructPostRequest(request, requestInfo);
        } else {
            httpRequest = constructGetRequest(request, requestInfo);
        }

        // 发起请求
        try {
            HttpResponse httpResponse = client.execute(httpRequest);
            HttpEntity entity = httpResponse.getEntity();
            String response = EntityUtils.toString(entity, StandardCharsets.UTF_8);
            log.info("WeChat url:{} response:{}", url, response);

            // 解析返回数据
            DataConverter<WeChatRequest<T>, T> converter;
            if (weChatApi.responseFormat() == ApiDataFormat.JSON) {
                converter = new JsonDataConverter<>(request.getResponseClass());
            } else {
                converter = new XmlDataConverter(request.getClass(), request.getResponseClass());
            }
            T weChatResponse = converter.deserialize(response);

            // 如果返回错误码，需要抛出异常
            if (!StringUtils.isBlank(weChatResponse.getReturnCode())
                    && !"SUCCESS".equalsIgnoreCase(weChatResponse.getReturnCode())) {
                throw new WeChatException(weChatResponse.getReturnCode(), weChatResponse.getReturnMsg());
            }

            return weChatResponse;
        } catch (IOException e) {
            throw new WeChatException("500", "网络访问异常！");
        }
    }


    private void signRequestIfNecessary(Object request) {
        if (!(request instanceof Signable)) {
            return;
        }

        Signable signable = (Signable) request;
        if (StringUtils.isBlank(signable.getSign())) {
            signable.setSign(sign(request));
        }
    }

    public String sign(Object obj) {
        return null;
    }

    public String signByXml(String xml) {
        return null;
    }

    public String decode(String code) {
        return null;
    }

    private HttpUriRequest constructGetRequest(Object request, RequestMetaInfo requestInfo) {
        String url = requestInfo.getWeChatApi().url();
        StringBuilder queryString = new StringBuilder();
        requestInfo.getParamInfo().forEach((filedName, fieldInfo) -> {
            try {
                String fieldValue = (String) fieldInfo.getGetter().invoke(request);
                queryString.append(filedName).append("=").append(StringUtils.trimToEmpty(fieldValue)).append("&");
            } catch (ReflectiveOperationException e) {
                throw new IllegalStateException("组织Get请求参数失败！", e);
            }
        });

        return new HttpGet(url + "?" + queryString.toString());
    }

    private HttpUriRequest constructPostRequest(WeChatRequest request, RequestMetaInfo requestInfo) {
        HttpPost post = new HttpPost(requestInfo.getWeChatApi().url());
        DataConverter converter;
        if (requestInfo.getWeChatApi().bodyFormat() == ApiDataFormat.XML) {
            post.setHeader("Content-Type", "application/xml;charset=UTF-8");
            converter = new XmlDataConverter(request.getClass(), null);
        } else {
            post.setHeader("Content-Type", "application/json;charset=UTF-8");
            converter = new JsonDataConverter(request.getResponseClass());
        }

        String body = converter.serialize(request);
        log.info("WeChat请求，body={}", body);

        post.setEntity(new StringEntity(body, StandardCharsets.UTF_8));
        return post;
    }
}
